﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace MSpecLauncher.Services
{
    /// <summary>
    /// Detects changes in the given files and trigger notification event.
    /// The event is triggered in a separate thread and after a period to wait
    /// changes to stabilize.
    /// </summary>
    public class AsyncChangesDetector : IFileChangesDetector
    {
        private const string TXT_FILE_NOT_FOUND = "The file to watch does not exist: {0}";

        private IAsyncTimer asyncTimer = null;
        private List<FileSystemWatcher> watchers = new List<FileSystemWatcher>();

        /// <summary>
        /// Event to notify changes in a file
        /// </summary>
        public event Action<string> FileChanged;

        /// <summary>
        /// If asyncTimer is given, the changes will wait for the specified time before launch the event.
        /// In this case, more than one change can be considered with the same event, and the event will be
        /// launch in a separated thread.
        /// If asyncTimer is null, the changes will be notified inmediatelly in the same thread.
        /// </summary>
        public AsyncChangesDetector(IAsyncTimer asyncTimer)
        {
            this.asyncTimer = asyncTimer;
        }

        /// <summary>
        /// Add a new file to monitor
        /// </summary>
        public void AddFileToWatch(string fileToWatch)
        {
            if (File.Exists(fileToWatch))
            {
                // Extract directory and file names
                string directory = Path.GetDirectoryName(fileToWatch);
                string fileNameOnly = Path.GetFileName(fileToWatch);

                // Create watcher for the given file, adding a callback for changes
                FileSystemWatcher watcher = new FileSystemWatcher(directory, fileNameOnly);
                watcher.Changed += new FileSystemEventHandler(watcher_Changed);
                watcher.NotifyFilter = NotifyFilters.LastWrite;
                watcher.EnableRaisingEvents = true;

                // Add the watcher to the collection
                this.watchers.Add(watcher);
            }
            else
            {
                throw new ApplicationException(string.Format(TXT_FILE_NOT_FOUND, fileToWatch));
            }
        }

        /// <summary>
        /// Clear all the watches
        /// </summary>
        public void ClearWatches()
        {
            foreach (FileSystemWatcher watcher in this.watchers)
            {
                watcher.EnableRaisingEvents = false;
                watcher.Dispose();
            }

            this.watchers.Clear();
        }

        /// <summary>
        /// Stops the timer for background execution, and notify potential customers
        /// </summary>
        private void TimerCallback(object fileChanged)
        {
            // Stop timer until next change
            this.asyncTimer.Stop();

            // Launch event to notify interested parties
            if (this.FileChanged != null) this.FileChanged(fileChanged.ToString());
        }

        private void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            // One file has changed

            if (this.asyncTimer == null)
            {
                // Launch event inmediatelly
                if (this.FileChanged != null) this.FileChanged(e.FullPath);
            }
            else
            {
                // The timer is started if previous changes have been detected but not yet executed.
                // In that case, this new changes will be considered as well in the started timer
                if (!this.asyncTimer.IsStarted)
                {
                    lock (this.asyncTimer)
                    {
                        if (!this.asyncTimer.IsStarted)
                        {
                            // Set the timer to launch in some seconds (to include maybe more changes)
                            this.asyncTimer.Start(this.TimerCallback, e.FullPath);
                        }
                    }
                }
            }
        }


    }
}
